package mrpanyu.guitool.dbcodegen.processor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import org.mvel2.MVEL;
import org.mvel2.templates.TemplateRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import mrpanyu.guitool.dbcodegen.model.Database;
import mrpanyu.guitool.dbcodegen.model.Table;
import mrpanyu.guitool.dbcodegen.util.DbCodeGeneratorUtils;

/**
 * 使用模板引擎生成文件的处理组件
 */
public abstract class AbstractTemplateGenerator implements DbModelProcessor {

	public enum TemplateFor {
		/** 每张表输出一个文件 */
		TABLE,
		/** 整个库输出一个文件 */
		DATABASE
	}
	
	protected Logger logger = LoggerFactory.getLogger(getClass());

	protected TemplateFor templateFor = TemplateFor.TABLE;
	protected String templateFile;
	protected String outputFile;
	protected String outputWhen;
	protected String outputEncoding = "UTF-8";
	protected Properties params;

	/** 除数据库元模型以外的额外参数，可用于输出路径表达式以及模板文件中 */
	public Properties getParams() {
		return params;
	}

	/** 除数据库元模型以外的额外参数，可用于输出路径表达式以及模板文件中 */
	public void setParams(Properties params) {
		this.params = params;
	}

	/**
	 * 模板对应的元模型维度
	 */
	public TemplateFor getTemplateFor() {
		return templateFor;
	}

	/**
	 * 模板对应的元模型维度
	 */
	public void setTemplateFor(TemplateFor templateFor) {
		this.templateFor = templateFor;
	}

	/** 模板文件路径 */
	public String getTemplateFile() {
		return templateFile;
	}

	/** 模板文件路径 */
	public void setTemplateFile(String templateFile) {
		this.templateFile = templateFile;
	}

	/**
	 * 输出文件路径，可以包含MVEL模板表达式
	 */
	public String getOutputFile() {
		return outputFile;
	}

	/**
	 * 输出文件路径，可以包含MVEL模板表达式
	 */
	public void setOutputFile(String outputFile) {
		this.outputFile = outputFile;
	}

	/**
	 * MVEL表达式，返回一个boolean类型表示当前表是否要执行这个模板。为空表示不区分情况都需要执行。
	 */
	public String getOutputWhen() {
		return outputWhen;
	}

	/**
	 * MVEL表达式，返回一个boolean类型表示当前表是否要执行这个模板。为空表示不区分情况都需要执行。
	 */
	public void setOutputWhen(String outputWhen) {
		this.outputWhen = outputWhen;
	}

	public String getOutputEncoding() {
		return outputEncoding;
	}

	public void setOutputEncoding(String outputEncoding) {
		this.outputEncoding = outputEncoding;
	}

	@Override
	public Database process(Database model) throws Exception {
		initialize();
		String templateContent = DbCodeGeneratorUtils.loadResource(templateFile, "UTF-8");
		if (templateFor == TemplateFor.DATABASE) {
			logger.info("正在为数据库执行模板：" + templateFile);
			Map<String, Object> context = new HashMap<String, Object>();
			context.put("model", model);
			context.put("database", model);
			context.put("params", params);
			doGenerate(context, templateContent);
		} else {
			for (Table table : model.getTables()) {
				logger.info("正在为表" + table.getTableName() + "执行模板：" + templateFile);
				Map<String, Object> context = new HashMap<String, Object>();
				context.put("model", model);
				context.put("database", model);
				context.put("table", table);
				context.put("params", params);
				doGenerate(context, templateContent);
			}
		}
		return model;
	}

	private void doGenerate(Map<String, Object> context, String templateContent) throws Exception {
		boolean doOutput = true;
		if (DbCodeGeneratorUtils.isNotBlank(outputWhen)) {
			doOutput = evalOutputWhen(context);
		}
		if (doOutput) {
			String outputPath = evalOutputFile(context);
			logger.info("写入文件：" + outputPath);
			File outFile = new File(outputPath);
			outFile.getParentFile().mkdirs();
			Writer output = new OutputStreamWriter(new FileOutputStream(outputPath), outputEncoding);
			try {
				applyTemplate(context, templateContent, output);
			} finally {
				output.close();
			}
		}
	}

	/**
	 * 执行前初始化方法，子类可以重写
	 */
	protected void initialize() throws Exception {
		// default empty
	}

	/**
	 * 由子类实现的模板引擎执行方法
	 * 
	 * @param context         模板变量
	 * @param templateContent 模板内容
	 * @param output          输出用的Writer
	 */
	protected abstract void applyTemplate(Map<String, Object> context, String templateContent, Writer output)
			throws Exception;

	/**
	 * 默认用MVEL表达式解析outputWhen，子类可以重写
	 */
	protected boolean evalOutputWhen(Map<String, Object> context) {
		return (Boolean) MVEL.eval(outputWhen, context);
	}

	/**
	 * 默认用MVEL模板表达式解析outputFile路径，子类可以重写
	 */
	protected String evalOutputFile(Map<String, Object> context) {
		return TemplateRuntime.eval(outputFile, context).toString();
	}

}
